# examples/deep_learning_forecasting.py
"""
深度学习时间序列预测示例
使用多种深度学习模型预测股价和金融时间序列
"""

import sys
import numpy as np
import pandas as pd
from pathlib import Path
from datetime import datetime, timedelta
from typing import Dict, List, Tuple, Optional
import warnings
warnings.filterwarnings('ignore')

# 深度学习相关库
try:
    import tensorflow as tf
    from tensorflow.keras.models import Sequential, Model
    from tensorflow.keras.layers import (LSTM, GRU, Dense, Dropout, Conv1D, 
                                       MaxPooling1D, Flatten, Input, 
                                       MultiHeadAttention, LayerNormalization,
                                       GlobalAveragePooling1D, TimeDistributed,
                                       Bidirectional, Concatenate)
    from tensorflow.keras.optimizers import Adam
    from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
    from sklearn.preprocessing import MinMaxScaler, StandardScaler
    from sklearn.metrics import mean_squared_error, mean_absolute_error
    TENSORFLOW_AVAILABLE = True
except ImportError:
    TENSORFLOW_AVAILABLE = False
    print("⚠️ TensorFlow未安装，深度学习模型将无法使用")

# 添加项目路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))

from src.services.market_data_service import MarketDataService

class TimeSeriesPreprocessor:
    """时间序列数据预处理器"""
    
    def __init__(self, sequence_length: int = 60, prediction_horizon: int = 1):
        self.sequence_length = sequence_length
        self.prediction_horizon = prediction_horizon
        self.scaler = None
        self.feature_names = []
        
    def create_features(self, data: pd.DataFrame) -> pd.DataFrame:
        """创建时间序列特征"""
        df = data.copy()
        
        # 基础价格特征
        df['returns'] = df['close'].pct_change()
        df['log_returns'] = np.log(df['close'] / df['close'].shift(1))
        df['high_low_ratio'] = df['high'] / df['low']
        df['open_close_ratio'] = df['open'] / df['close']
        
        # 技术指标
        # 移动平均
        for window in [5, 10, 20, 50]:
            df[f'sma_{window}'] = df['close'].rolling(window).mean()
            df[f'ema_{window}'] = df['close'].ewm(span=window).mean()
            
        # 波动率
        df['volatility_5'] = df['returns'].rolling(5).std()
        df['volatility_20'] = df['returns'].rolling(20).std()
        
        # RSI
        delta = df['close'].diff()
        gain = delta.where(delta > 0, 0)
        loss = -delta.where(delta < 0, 0)
        avg_gain = gain.rolling(14).mean()
        avg_loss = loss.rolling(14).mean()
        rs = avg_gain / avg_loss
        df['rsi'] = 100 - (100 / (1 + rs))
        
        # MACD
        ema12 = df['close'].ewm(span=12).mean()
        ema26 = df['close'].ewm(span=26).mean()
        df['macd'] = ema12 - ema26
        df['macd_signal'] = df['macd'].ewm(span=9).mean()
        df['macd_histogram'] = df['macd'] - df['macd_signal']
        
        # 布林带
        sma20 = df['close'].rolling(20).mean()
        std20 = df['close'].rolling(20).std()
        df['bb_upper'] = sma20 + (std20 * 2)
        df['bb_lower'] = sma20 - (std20 * 2)
        df['bb_width'] = df['bb_upper'] - df['bb_lower']
        df['bb_position'] = (df['close'] - df['bb_lower']) / df['bb_width']
        
        # 成交量特征
        df['volume_sma'] = df['volume'].rolling(20).mean()
        df['volume_ratio'] = df['volume'] / df['volume_sma']
        
        # 时间特征
        df['day_of_week'] = df.index.dayofweek
        df['month'] = df.index.month
        df['quarter'] = df.index.quarter
        
        return df
    
    def create_sequences(self, data: pd.DataFrame, target_column: str = 'close') -> Tuple[np.ndarray, np.ndarray]:
        """创建序列数据"""
        # 删除无用列并填充缺失值
        feature_cols = [col for col in data.columns if col not in ['symbol', 'source']]
        df = data[feature_cols].fillna(method='ffill').fillna(method='bfill')
        
        # 数据标准化
        self.scaler = MinMaxScaler()
        scaled_data = self.scaler.fit_transform(df)
        
        self.feature_names = feature_cols
        
        # 获取目标列索引
        target_idx = feature_cols.index(target_column)
        
        X, y = [], []
        
        for i in range(self.sequence_length, len(scaled_data) - self.prediction_horizon + 1):
            # 输入序列
            X.append(scaled_data[i-self.sequence_length:i])
            # 目标值
            y.append(scaled_data[i+self.prediction_horizon-1, target_idx])
        
        return np.array(X), np.array(y)
    
    def inverse_transform_predictions(self, predictions: np.ndarray, target_column: str = 'close') -> np.ndarray:
        """反向转换预测结果"""
        if self.scaler is None:
            raise ValueError("数据未经过标准化处理")
        
        # 创建与原始数据相同形状的数组
        target_idx = self.feature_names.index(target_column)
        dummy_array = np.zeros((len(predictions), len(self.feature_names)))
        dummy_array[:, target_idx] = predictions
        
        # 反向转换
        inverse_transformed = self.scaler.inverse_transform(dummy_array)
        return inverse_transformed[:, target_idx]

class DeepLearningModels:
    """深度学习模型集合"""
    
    @staticmethod
    def create_lstm_model(input_shape: Tuple, units: int = 50, dropout_rate: float = 0.2) -> Model:
        """创建LSTM模型"""
        model = Sequential([
            LSTM(units, return_sequences=True, input_shape=input_shape),
            Dropout(dropout_rate),
            LSTM(units, return_sequences=True),
            Dropout(dropout_rate),
            LSTM(units),
            Dropout(dropout_rate),
            Dense(25),
            Dense(1)
        ])
        
        model.compile(optimizer=Adam(learning_rate=0.001), loss='mse', metrics=['mae'])
        return model
    
    @staticmethod
    def create_gru_model(input_shape: Tuple, units: int = 50, dropout_rate: float = 0.2) -> Model:
        """创建GRU模型"""
        model = Sequential([
            GRU(units, return_sequences=True, input_shape=input_shape),
            Dropout(dropout_rate),
            GRU(units, return_sequences=True),
            Dropout(dropout_rate),
            GRU(units),
            Dropout(dropout_rate),
            Dense(25),
            Dense(1)
        ])
        
        model.compile(optimizer=Adam(learning_rate=0.001), loss='mse', metrics=['mae'])
        return model
    
    @staticmethod
    def create_cnn_lstm_model(input_shape: Tuple) -> Model:
        """创建CNN-LSTM混合模型"""
        model = Sequential([
            Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=input_shape),
            Conv1D(filters=64, kernel_size=3, activation='relu'),
            MaxPooling1D(pool_size=2),
            LSTM(50, return_sequences=True),
            Dropout(0.2),
            LSTM(50),
            Dropout(0.2),
            Dense(25),
            Dense(1)
        ])
        
        model.compile(optimizer=Adam(learning_rate=0.001), loss='mse', metrics=['mae'])
        return model
    
    @staticmethod
    def create_bidirectional_lstm_model(input_shape: Tuple, units: int = 50) -> Model:
        """创建双向LSTM模型"""
        model = Sequential([
            Bidirectional(LSTM(units, return_sequences=True), input_shape=input_shape),
            Dropout(0.2),
            Bidirectional(LSTM(units, return_sequences=True)),
            Dropout(0.2),
            Bidirectional(LSTM(units)),
            Dropout(0.2),
            Dense(25),
            Dense(1)
        ])
        
        model.compile(optimizer=Adam(learning_rate=0.001), loss='mse', metrics=['mae'])
        return model
    
    @staticmethod
    def create_attention_lstm_model(input_shape: Tuple) -> Model:
        """创建带注意力机制的LSTM模型"""
        inputs = Input(shape=input_shape)
        
        # LSTM层
        lstm_out = LSTM(64, return_sequences=True)(inputs)
        lstm_out = Dropout(0.2)(lstm_out)
        
        # 多头注意力
        attention_out = MultiHeadAttention(num_heads=4, key_dim=64)(lstm_out, lstm_out)
        attention_out = LayerNormalization()(attention_out + lstm_out)
        
        # 全局平均池化
        pooling_out = GlobalAveragePooling1D()(attention_out)
        
        # 输出层
        dense_out = Dense(50, activation='relu')(pooling_out)
        dense_out = Dropout(0.2)(dense_out)
        outputs = Dense(1)(dense_out)
        
        model = Model(inputs=inputs, outputs=outputs)
        model.compile(optimizer=Adam(learning_rate=0.001), loss='mse', metrics=['mae'])
        
        return model
    
    @staticmethod
    def create_ensemble_model(input_shape: Tuple) -> Model:
        """创建集成模型"""
        inputs = Input(shape=input_shape)
        
        # LSTM分支
        lstm_branch = LSTM(50, return_sequences=True)(inputs)
        lstm_branch = LSTM(50)(lstm_branch)
        lstm_branch = Dense(25, activation='relu')(lstm_branch)
        
        # GRU分支  
        gru_branch = GRU(50, return_sequences=True)(inputs)
        gru_branch = GRU(50)(gru_branch)
        gru_branch = Dense(25, activation='relu')(gru_branch)
        
        # CNN分支
        cnn_branch = Conv1D(64, 3, activation='relu')(inputs)
        cnn_branch = MaxPooling1D(2)(cnn_branch)
        cnn_branch = Flatten()(cnn_branch)
        cnn_branch = Dense(25, activation='relu')(cnn_branch)
        
        # 合并分支
        merged = Concatenate()([lstm_branch, gru_branch, cnn_branch])
        merged = Dense(50, activation='relu')(merged)
        merged = Dropout(0.2)(merged)
        outputs = Dense(1)(merged)
        
        model = Model(inputs=inputs, outputs=outputs)
        model.compile(optimizer=Adam(learning_rate=0.001), loss='mse', metrics=['mae'])
        
        return model

class ModelTrainer:
    """模型训练器"""
    
    def __init__(self):
        self.models = {}
        self.histories = {}
        
    def train_model(self, model: Model, X_train: np.ndarray, y_train: np.ndarray,
                   X_val: np.ndarray, y_val: np.ndarray, 
                   model_name: str, epochs: int = 100) -> Dict:
        """训练模型"""
        
        # 回调函数
        callbacks = [
            EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True),
            ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=1e-7)
        ]
        
        print(f"🧠 训练 {model_name} 模型...")
        
        try:
            # 训练模型
            history = model.fit(
                X_train, y_train,
                epochs=epochs,
                batch_size=32,
                validation_data=(X_val, y_val),
                callbacks=callbacks,
                verbose=0
            )
            
            # 预测
            train_pred = model.predict(X_train)
            val_pred = model.predict(X_val)
            
            # 计算指标
            train_mse = mean_squared_error(y_train, train_pred)
            val_mse = mean_squared_error(y_val, val_pred)
            train_mae = mean_absolute_error(y_train, train_pred)
            val_mae = mean_absolute_error(y_val, val_pred)
            
            # 保存模型和历史
            self.models[model_name] = model
            self.histories[model_name] = history
            
            print(f"     ✅ {model_name}: Val MSE = {val_mse:.6f}, Val MAE = {val_mae:.6f}")
            
            return {
                'model': model,
                'history': history,
                'train_mse': train_mse,
                'val_mse': val_mse,
                'train_mae': train_mae,
                'val_mae': val_mae,
                'train_predictions': train_pred.flatten(),
                'val_predictions': val_pred.flatten()
            }
            
        except Exception as e:
            print(f"     ❌ {model_name} 训练失败: {e}")
            return None
    
    def compare_models(self, results: Dict) -> pd.DataFrame:
        """比较模型性能"""
        comparison_data = []
        
        for model_name, result in results.items():
            if result is not None:
                comparison_data.append({
                    'Model': model_name,
                    'Train MSE': f"{result['train_mse']:.6f}",
                    'Val MSE': f"{result['val_mse']:.6f}",
                    'Train MAE': f"{result['train_mae']:.6f}",
                    'Val MAE': f"{result['val_mae']:.6f}",
                    'Overfitting': f"{(result['val_mse'] / result['train_mse'] - 1) * 100:.1f}%"
                })
        
        return pd.DataFrame(comparison_data)
    
    def plot_training_history(self, model_name: str):
        """绘制训练历史"""
        if model_name not in self.histories:
            print(f"❌ 模型 {model_name} 的训练历史不存在")
            return
        
        try:
            import matplotlib.pyplot as plt
            
            history = self.histories[model_name]
            
            fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
            
            # 损失函数
            ax1.plot(history.history['loss'], label='Training Loss')
            ax1.plot(history.history['val_loss'], label='Validation Loss')
            ax1.set_title(f'{model_name} - Loss')
            ax1.set_xlabel('Epoch')
            ax1.set_ylabel('Loss')
            ax1.legend()
            ax1.grid(True)
            
            # MAE
            ax2.plot(history.history['mae'], label='Training MAE')
            ax2.plot(history.history['val_mae'], label='Validation MAE')
            ax2.set_title(f'{model_name} - MAE')
            ax2.set_xlabel('Epoch')
            ax2.set_ylabel('MAE')
            ax2.legend()
            ax2.grid(True)
            
            plt.tight_layout()
            plt.show()
            
        except ImportError:
            print("⚠️ matplotlib未安装，无法绘制训练历史")

class MultiStepPredictor:
    """多步预测器"""
    
    def __init__(self, model: Model, preprocessor: TimeSeriesPreprocessor):
        self.model = model
        self.preprocessor = preprocessor
    
    def predict_sequence(self, last_sequence: np.ndarray, steps: int) -> np.ndarray:
        """递归预测多个时间步"""
        predictions = []
        current_sequence = last_sequence.copy()
        
        for _ in range(steps):
            # 预测下一个值
            next_pred = self.model.predict(current_sequence.reshape(1, *current_sequence.shape), verbose=0)[0, 0]
            predictions.append(next_pred)
            
            # 更新序列：移除第一个值，添加预测值到最后
            current_sequence = np.roll(current_sequence, -1, axis=0)
            current_sequence[-1, 0] = next_pred  # 假设price是第一个特征
        
        return np.array(predictions)
    
    def predict_multiple_horizons(self, last_sequence: np.ndarray, horizons: List[int]) -> Dict[int, float]:
        """预测多个时间范围"""
        results = {}
        
        for horizon in horizons:
            predictions = self.predict_sequence(last_sequence, horizon)
            # 反向转换预测结果
            pred_prices = self.preprocessor.inverse_transform_predictions(predictions)
            results[horizon] = pred_prices[-1]  # 最后一个预测值
        
        return results

class RiskAnalyzer:
    """风险分析器"""
    
    @staticmethod
    def calculate_var(returns: np.ndarray, confidence_level: float = 0.05) -> float:
        """计算风险价值(VaR)"""
        return np.percentile(returns, confidence_level * 100)
    
    @staticmethod
    def calculate_expected_shortfall(returns: np.ndarray, confidence_level: float = 0.05) -> float:
        """计算期望损失(ES)"""
        var = RiskAnalyzer.calculate_var(returns, confidence_level)
        return returns[returns <= var].mean()
    
    @staticmethod
    def monte_carlo_simulation(model: Model, last_sequence: np.ndarray, 
                             steps: int, simulations: int = 1000) -> np.ndarray:
        """蒙特卡洛模拟"""
        results = []
        
        for _ in range(simulations):
            # 添加随机噪声
            noisy_sequence = last_sequence + np.random.normal(0, 0.01, last_sequence.shape)
            
            # 预测
            prediction = model.predict(noisy_sequence.reshape(1, *noisy_sequence.shape), verbose=0)[0, 0]
            results.append(prediction)
        
        return np.array(results)
    
    @staticmethod
    def analyze_prediction_uncertainty(predictions: np.ndarray) -> Dict:
        """分析预测不确定性"""
        return {
            'mean': np.mean(predictions),
            'std': np.std(predictions),
            'var_95': np.percentile(predictions, 5),
            'var_99': np.percentile(predictions, 1),
            'confidence_interval_95': (np.percentile(predictions, 2.5), np.percentile(predictions, 97.5))
        }

def create_prediction_report(symbol: str, current_price: float, 
                           predictions: Dict, uncertainty_analysis: Dict) -> str:
    """创建预测报告"""
    
    report = f"""
📈 {symbol} 股价预测报告
{'='*50}

当前价格: ${current_price:.2f}

🔮 预测结果:
"""
    
    for horizon, pred_price in predictions.items():
        change_pct = ((pred_price - current_price) / current_price) * 100
        trend = "📈" if change_pct > 0 else "📉"
        report += f"   {horizon}日后: ${pred_price:.2f} ({change_pct:+.1f}%) {trend}\n"
    
    report += f"""
🎯 不确定性分析:
   预测均值: ${uncertainty_analysis['mean']:.2f}
   标准差: ${uncertainty_analysis['std']:.2f}
   95%置信区间: ${uncertainty_analysis['confidence_interval_95'][0]:.2f} - ${uncertainty_analysis['confidence_interval_95'][1]:.2f}
   
⚠️  风险提示:
   5%分位数 (VaR 95%): ${uncertainty_analysis['var_95']:.2f}
   1%分位数 (VaR 99%): ${uncertainty_analysis['var_99']:.2f}

💡 投资建议:
   基于深度学习模型的预测结果仅供参考
   请结合基本面分析和市场环境做出投资决策
   注意控制风险，合理配置资产
"""
    
    return report

def main():
    """主函数演示深度学习时间序列预测"""
    
    if not TENSORFLOW_AVAILABLE:
        print("❌ TensorFlow未安装，无法运行深度学习示例")
        print("   请运行: pip install tensorflow")
        return
    
    print("🧠 深度学习时间序列预测系统")
    print("=" * 50)
    
    # 初始化服务
    market_service = MarketDataService()
    
    # 获取数据
    symbol = 'AAPL'
    print(f"📊 获取 {symbol} 历史数据...")
    
    start_date = datetime.now() - timedelta(days=1000)  # 获取更多数据用于深度学习
    end_date = datetime.now()
    
    data = market_service.get_stock_price(
        symbol,
        start_date=start_date,
        end_date=end_date,
        interval='1d'
    )
    
    if data.empty:
        print("❌ 未能获取数据")
        return
    
    print(f"✅ 获取到 {len(data)} 条数据记录")
    
    try:
        # 数据预处理
        print("\n🔧 数据预处理...")
        preprocessor = TimeSeriesPreprocessor(sequence_length=60, prediction_horizon=1)
        
        # 特征工程
        processed_data = preprocessor.create_features(data)
        print(f"   创建了 {len(processed_data.columns)} 个特征")
        
        # 创建序列数据
        X, y = preprocessor.create_sequences(processed_data, target_column='close')
        print(f"   序列数据形状: X={X.shape}, y={y.shape}")
        
        # 分割数据
        train_size = int(0.7 * len(X))
        val_size = int(0.15 * len(X))
        
        X_train = X[:train_size]
        y_train = y[:train_size]
        X_val = X[train_size:train_size + val_size]
        y_val = y[train_size:train_size + val_size]
        X_test = X[train_size + val_size:]
        y_test = y[train_size + val_size:]
        
        print(f"   训练集: {X_train.shape[0]} 样本")
        print(f"   验证集: {X_val.shape[0]} 样本")
        print(f"   测试集: {X_test.shape[0]} 样本")
        
        # 创建模型
        input_shape = (X.shape[1], X.shape[2])
        models_config = {
            'LSTM': DeepLearningModels.create_lstm_model(input_shape),
            'GRU': DeepLearningModels.create_gru_model(input_shape),
            'CNN_LSTM': DeepLearningModels.create_cnn_lstm_model(input_shape),
            'BiLSTM': DeepLearningModels.create_bidirectional_lstm_model(input_shape),
            'Attention_LSTM': DeepLearningModels.create_attention_lstm_model(input_shape),
            'Ensemble': DeepLearningModels.create_ensemble_model(input_shape)
        }
        
        # 训练模型
        print(f"\n🧠 训练 {len(models_config)} 个深度学习模型...")
        trainer = ModelTrainer()
        results = {}
        
        for model_name, model in models_config.items():
            result = trainer.train_model(
                model, X_train, y_train, X_val, y_val, 
                model_name, epochs=50
            )
            if result is not None:
                results[model_name] = result
        
        # 模型性能比较
        if results:
            print(f"\n📊 模型性能对比:")
            comparison_df = trainer.compare_models(results)
            print(comparison_df.to_string(index=False))
            
            # 找出最佳模型
            best_model_name = min(results.keys(), 
                                key=lambda x: results[x]['val_mse'])
            best_model = results[best_model_name]['model']
            
            print(f"\n🏆 最佳模型: {best_model_name}")
            
            # 测试集评估
            print(f"\n🎯 测试集评估:")
            test_predictions = best_model.predict(X_test)
            test_mse = mean_squared_error(y_test, test_predictions)
            test_mae = mean_absolute_error(y_test, test_predictions)
            
            print(f"   测试集 MSE: {test_mse:.6f}")
            print(f"   测试集 MAE: {test_mae:.6f}")
            
            # 多步预测
            print(f"\n🔮 多步预测分析...")
            last_sequence = X_test[-1]
            multi_step_predictor = MultiStepPredictor(best_model, preprocessor)
            
            horizons = [1, 3, 5, 10, 20]
            predictions = multi_step_predictor.predict_multiple_horizons(last_sequence, horizons)
            
            current_price = processed_data['close'].iloc[-1]
            
            # 不确定性分析
            print(f"   进行蒙特卡洛模拟...")
            mc_predictions = RiskAnalyzer.monte_carlo_simulation(
                best_model, last_sequence, steps=1, simulations=1000
            )
            
            # 反向转换蒙特卡洛预测结果
            mc_prices = preprocessor.inverse_transform_predictions(mc_predictions)
            uncertainty_analysis = RiskAnalyzer.analyze_prediction_uncertainty(mc_prices)
            
            # 生成预测报告
            report = create_prediction_report(symbol, current_price, predictions, uncertainty_analysis)
            print(report)
            
            # 风险分析
            print(f"\n⚠️  风险分析:")
            # 计算历史收益率
            historical_returns = processed_data['returns'].dropna()
            var_95 = RiskAnalyzer.calculate_var(historical_returns, 0.05)
            es_95 = RiskAnalyzer.calculate_expected_shortfall(historical_returns, 0.05)
            
            print(f"   历史VaR (95%): {var_95:.4f}")
            print(f"   历史ES (95%): {es_95:.4f}")
            
            # 模型解释性分析
            print(f"\n🔍 特征重要性分析:")
            # 这里可以添加SHAP或LIME等模型解释性分析
            print(f"   注意: 深度学习模型的特征重要性分析需要额外的工具")
            print(f"   建议使用SHAP、LIME等工具进行模型解释")
            
            # 保存模型
            model_save_path = f"models/{symbol}_{best_model_name}_model.h5"
            Path("models").mkdir(exist_ok=True)
            
            try:
                best_model.save(model_save_path)
                print(f"\n💾 最佳模型已保存到: {model_save_path}")
            except Exception as e:
                print(f"\n❌ 模型保存失败: {e}")
            
            # 实时预测建议
            print(f"\n💡 实时预测建议:")
            next_day_pred = predictions[1]
            if next_day_pred > current_price * 1.02:
                print(f"   🟢 强烈看涨: 预测明日上涨 {((next_day_pred/current_price)-1)*100:.1f}%")
            elif next_day_pred > current_price * 1.005:
                print(f"   🟡 温和看涨: 预测明日上涨 {((next_day_pred/current_price)-1)*100:.1f}%")
            elif next_day_pred < current_price * 0.98:
                print(f"   🔴 强烈看跌: 预测明日下跌 {((next_day_pred/current_price)-1)*100:.1f}%")
            elif next_day_pred < current_price * 0.995:
                print(f"   🟠 温和看跌: 预测明日下跌 {((next_day_pred/current_price)-1)*100:.1f}%")
            else:
                print(f"   ⚪ 中性: 预测明日波动较小")
        
        else:
            print("❌ 所有模型训练失败")
            
    except Exception as e:
        print(f"❌ 深度学习预测过程中发生错误: {e}")
        import traceback
        traceback.print_exc()

if __name__ == "__main__":
    main()